﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using RaytracerLib;
using RaytracerLib.Layers;
using RaytracerLib.RenderableObjects;
using RaytracerLib.Volumes;
using TomanuExtensions;
using System.Drawing;
using RaytracerLib.Materials;
using RaytracerLib.Lights;
using RaytracerLib.Script;
using RaytracerLib.MathLib;
using Raytracer.PropertyGridDecorators;
using RaytracerLib.Cameras;
using RaytracerLib.Postprocessing;

namespace Raytracer
{
    public static class TreeManager
    {
        private static TreeNode s_active_camera_node;

        private static string GetTreeName(object a_tag, TreeNode a_node = null)
        {
            if (a_tag is Scene)
            {
                var scene = a_tag as Scene;
                return String.Format("Scene ('{0}')", scene.Name);
            }
            else if (a_tag is RenderableObject)
            {
                var renderable_object = a_tag as RenderableObject;
                return String.Format("{1} [{0}]", renderable_object.GetType().Name,
                    renderable_object.Name);
            }
            else if (a_tag is Light)
            {
                var light = a_tag as Light;
                return String.Format("{0} [{1}]", light.Name, light.GetType().Name);
            }
            else if (a_tag is Camera)
            {
                var camera = a_tag as Camera;
                return String.Format("{1}{2} [{0}]", camera.GetType().Name, 
                    camera.Name, camera.Active ? "*" : "");
            }
            else if (a_tag is Film)
            {
                var film = a_tag as Film;
                return String.Format("{0} ({1}x{2})", film.Name, film.Width, film.Height);
            }
            else if (a_tag is Volume)
            {
                return a_tag.GetType().Name;
            }
            else if (a_tag is Layer)
            {
                string result = a_tag.GetType().Name;
                var layer = a_tag as Layer;
                if (layer.Parent != null)
                {
                    if (layer.Parent.GetType().IsDerivedFrom(
                        typeof(TransparentOperationLayer)))
                    {
                        if (layer.Parent.Children.IndexOf(layer) == 2)
                            result = "Alpha: " + result;
                    }
                }

                return result;
            }
            else if (a_tag is MaterialsList)
            {
                var materials = a_tag as MaterialsList;
                return String.Format("Materials ({0})", materials.Count());
            }
            else if (a_tag is FilmsList)
            {
                var films = a_tag as FilmsList;
                return String.Format("Films ({0})", films.Count());
            }
            else if (a_tag is LightsList)
            {
                var lights = a_tag as LightsList;
                return String.Format("Lights ({0})", lights.Count());
            }
            else if (a_tag is CamerasList)
            {
                var cameras = a_tag as CamerasList;
                return String.Format("Cameras ({0})", cameras.Count());
            }
            else if (a_tag is RenderableObjectList)
            {
                var objects = a_tag as RenderableObjectList;
                return String.Format("Objects ({0})", objects.Count());
            }
            else if (a_tag is Material)
            {
                var material = a_tag as Material;
                return material.Name;
            }
            else if (a_tag is GammaCorrector)
            {
                var gc = a_tag as GammaCorrector;
                return String.Format("{0}, ({1})", gc.GetType().Name, gc.GammaFactor);
            }
            else if (a_tag is Postprocessor)
            {
                return a_tag.GetType().Name;
            }
            else
                return a_node.Text;
        }

        private static IEnumerable<TreeNode> NodesAsList(TreeNodeCollection a_nodes)
        {
            foreach (var node in a_nodes.Cast<TreeNode>())
            {
                yield return node;

                foreach (var n in NodesAsList(node.Nodes))
                    yield return node;
            }
        }

        public static void BuildSceneTree(Scene a_scene, TreeView a_tree_view)
        {
            a_tree_view.BeginUpdate();

            try
            {
                a_tree_view.Nodes.Clear();

                var scene_node = a_tree_view.Nodes.Add(GetTreeName(a_scene));
                scene_node.Tag = a_scene;

                var render_options_node = scene_node.Nodes.Add("Render options");
                render_options_node.Tag = a_scene.RenderOptions;

                var materials_node = scene_node.Nodes.Add(GetTreeName(a_scene.Materials));
                materials_node.Tag = a_scene.Materials;

                foreach (var material in a_scene.Materials.OrderBy(m => m.Name))
                {
                    var material_node = materials_node.Nodes.Add(GetTreeName(material));
                    material_node.Tag = material;

                    if (material.Ambient != null)
                    {
                        var ambient = material_node.Nodes.Add("Ambient");
                        BuildMaterialTree(ambient, material.Ambient);
                    }

                    if (material.Diffuse != null)
                    {
                        var diffuse = material_node.Nodes.Add("Diffuse");
                        BuildMaterialTree(diffuse, material.Diffuse);
                    }

                    if (material.SpecularHighlight != null)
                    {
                        var specular = material_node.Nodes.Add("Specular Highlight ");
                        BuildMaterialTree(specular, material.SpecularHighlight);
                    }

                    if (material.SpecularHighlightExponent != null)
                    {
                        var specular_exponent = material_node.Nodes.Add(
                            "Specular Highlight Exponent");
                        BuildMaterialTree(specular_exponent, material.SpecularHighlightExponent);
                    }

                    if (material.ReflectionTransmittance != null)
                    {
                        var specular_reflection = material_node.Nodes.Add("ReflectionTransmittance");
                        BuildMaterialTree(specular_reflection, material.ReflectionTransmittance);
                    }

                    if (material.RefractionTransmittance != null)
                    {
                        var transmittance = material_node.Nodes.Add("RefractionTransmittance");
                        BuildMaterialTree(transmittance, material.RefractionTransmittance);
                    }

                    if (material.Tangents != null)
                    {
                        var tangents = material_node.Nodes.Add("Tangents");
                        BuildMaterialTree(tangents, material.Tangents);
                    }
                }

                var films_node = scene_node.Nodes.Add(GetTreeName(a_scene.Films));
                films_node.Tag = a_scene.Films;

                foreach (var film in a_scene.Films.OrderBy(film => film.Name))
                {
                    var film_node = films_node.Nodes.Add(GetTreeName(film));
                    film_node.Tag = film;

                    if (film.Postprocessors.Any())
                    {
                        var post_list_node = film_node.Nodes.Add("Postprocessors");
                        post_list_node.Tag = film.Postprocessors;

                        foreach (var pp in film.Postprocessors)
                        {
                            var pp_node = post_list_node.Nodes.Add(GetTreeName(pp));
                            pp_node.Tag = pp;
                        }
                    }
                }

                var cameras_node = scene_node.Nodes.Add(GetTreeName(a_scene.Cameras));
                cameras_node.Tag = a_scene.Cameras;

                foreach (var camera in a_scene.Cameras.OrderBy(camera => camera.Name))
                {
                    var camera_node = cameras_node.Nodes.Add(GetTreeName(camera));
                    camera_node.Tag = camera;
                }

                s_active_camera_node = scene_node.Nodes.Add("Active camera");
                s_active_camera_node.Tag = a_scene.ActiveCamera;

                var objects_node = scene_node.Nodes.Add(GetTreeName(a_scene.RenderableObjects));
                objects_node.Tag = a_scene.RenderableObjects;

                foreach (var scene_object in a_scene.RenderableObjects.OrderBy(obj => obj.Name))
                {
                    var scene_object_node = objects_node.Nodes.Add(GetTreeName(scene_object));
                    scene_object_node.Tag = scene_object;
                }

                var lights_node = scene_node.Nodes.Add(GetTreeName(a_scene.Lights));
                lights_node.Tag = a_scene.Lights;

                foreach (var light in a_scene.Lights.OrderBy(light => light.Name))
                {
                    var light_node = lights_node.Nodes.Add(GetTreeName(light));
                    light_node.Tag = light;
                }

                scene_node.Expand();
                render_options_node.Expand();
                materials_node.Expand();
                objects_node.Expand();
                lights_node.Expand();

                a_tree_view.SelectedNode = render_options_node;

            }
            finally
            {
                a_tree_view.EndUpdate();
            }
        }

        public static void RefreshSceneTree(Scene a_scene, TreeView a_tree_view, TreeNodeCollection a_nodes = null)
        {
            if (a_nodes == null)
                a_nodes = a_tree_view.Nodes;

            foreach (var node in a_nodes.Cast<TreeNode>())
            {
                if (node.Tag != null)
                    node.Text = GetTreeName(node.Tag, node);

                if (node.Nodes.Count != 0)
                    RefreshSceneTree(a_scene, a_tree_view, node.Nodes);

                if (node == s_active_camera_node)
                    s_active_camera_node.Tag = a_scene.ActiveCamera;
            }
        }

        private static void BuildMaterialTree(TreeNode a_parent_node,
            IMaterialPart a_material_part)
        {
            var child_node = a_parent_node.Nodes.Add(GetTreeName(a_material_part));
            child_node.Tag = a_material_part;

            if (a_material_part is ProceduralLayer)
                BuildScriptParams(child_node, (a_material_part as ProceduralLayer).Params);
            if (a_material_part is ProceduralVolume)
                BuildScriptParams(child_node, (a_material_part as ProceduralVolume).Params);

            IEnumerable<IMaterialPart> children;
            if (a_material_part is Layer)
                children = (a_material_part as Layer).Children;
            else
                children = (a_material_part as Volume).Children;

            foreach (var child in children)
                BuildMaterialTree(child_node, child);
        }

        private static void BuildScriptParams(TreeNode a_parent_node, ScriptParams a_params)
        {
            foreach (var param in a_params)
            {
                ScriptParamProperties props = null;
                if (param.Value is ColorFloat)
                    props = new ScriptParamColorFloatProperties(param);
                else if (param.Value is Double)
                    props = new ScriptParamDoubleProperties(param);

                var child_node = a_parent_node.Nodes.Add(param.Name);
                child_node.Tag = param;
            }
        }

        public static void BuildIntersectionTree(Intersection a_intersection, TreeView a_tree_view, 
            TreeNodeCollection a_nodes = null, string a_name = null)
        {
            a_tree_view.BeginUpdate();

            try
            {
                if (a_nodes == null)
                {
                    a_nodes = a_tree_view.Nodes;
                    a_tree_view.Nodes.Clear();
                }

                if (a_name == null)
                    a_name = "Intersection";

                if (a_intersection == null)
                    a_name = a_name + " [null]";
                else if (!a_intersection.Exists)
                {
                    a_name = a_name + " [no intersection]";
                    a_intersection = null;
                }

                var intersection_node = a_nodes.Add(a_name);
                intersection_node.Tag = a_intersection;

                if (a_intersection == null)
                {
                    intersection_node.ForeColor = Color.LightGray;
                    return;
                }

                // Force calculation.
                a_intersection.Color.ToString();

                var scene_object_node = intersection_node.Nodes.Add("SceneObject - " + GetTreeName(a_intersection.SceneObject));
                scene_object_node.Tag = a_intersection.SceneObject;

                var source_ray_node = intersection_node.Nodes.Add("SourceRay");
                source_ray_node.Tag = a_intersection.SourceRay;

                var reflected_ray_node = intersection_node.Nodes.Add("ReflectedRay");
                reflected_ray_node.Tag = a_intersection.ReflectedRay;
                if (a_intersection.ReflectedRay == null)
                {
                    reflected_ray_node.Text += " [null]";
                    reflected_ray_node.ForeColor = Color.LightGray;
                }

                var refracted_ray_node = intersection_node.Nodes.Add("RefractedRay");
                refracted_ray_node.Tag = a_intersection.RefractedRay;
                if (a_intersection.RefractedRay == null)
                {
                    refracted_ray_node.Text += " [null]";
                    refracted_ray_node.ForeColor = Color.LightGray;
                }

                BuildIntersectionTree(a_intersection.ReflectedIntersection, a_tree_view,
                    intersection_node.Nodes, "ReflectedIntersection");

                BuildIntersectionTree(a_intersection.RefractedIntersection, a_tree_view,
                    intersection_node.Nodes, "RefractedIntersection");

                #if DEBUG
                var lights = a_intersection.GetLights();
                var lights_node = intersection_node.Nodes.Add(String.Format("Lights [{0}]", lights.Count));
                foreach (var light in lights)
                {
                    var light_node = lights_node.Nodes.Add(GetTreeName(light));
                    light_node.Tag = light;
                }
                #endif

                a_tree_view.SelectedNode = intersection_node;
                a_tree_view.ExpandAll();
            }
            finally
            {
                a_tree_view.EndUpdate();
            }
        }

        public static void AddItem(TreeNode a_parent, Object a_obj)
        {
            var node = a_parent.Nodes.Insert(0, GetTreeName(a_obj));
            node.Tag = a_obj;
            node.TreeView.SelectedNode = node;
            node.EnsureVisible();
        }

        public static void InsertAfterItem(TreeNode a_after, Object a_obj)
        {
            var node = a_after.Parent.Nodes.Insert(a_after.Index + 1, GetTreeName(a_obj));
            node.Tag = a_obj;
            node.TreeView.SelectedNode = node;
            node.EnsureVisible();
        }
    }
}